/******************************************************************************* * Copyright (c) 2000, 2011 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.corext.refactoring.structure; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.runtime.Assert; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IMethod; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.ITypeParameter; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.internal.ui.JavaPlugin; /** * Utilities to create mappings between type variables of different types in a type hierarchy. */ public final class TypeVariableUtil { /** * Returns the composition of two type variable mappings. The type variables signatures can have an arbitrary format. * * @param first * the first type variable mapping * @param second * the second type variable mapping * @return the possibly empty composed type variable mapping */ public static TypeVariableMaplet[] composeMappings(final TypeVariableMaplet[] first, final TypeVariableMaplet[] second) { Assert.isNotNull(first); Assert.isNotNull(second); if (first.length == 0) return first; else if (second.length == 0) return second; else { TypeVariableMaplet source= null; TypeVariableMaplet target= null; final Set<TypeVariableMaplet> set= new HashSet<TypeVariableMaplet>(first.length * second.length); for (int index= 0; index < first.length; index++) { for (int offset= 0; offset < second.length; offset++) { source= first[index]; target= second[offset]; if (source.getTargetIndex() == target.getSourceIndex() && source.getTargetName().equals(target.getSourceName())) set.add(new TypeVariableMaplet(source.getSourceName(), index, target.getTargetName(), offset)); } } final TypeVariableMaplet[] mapping= new TypeVariableMaplet[set.size()]; set.toArray(mapping); return mapping; } } /** * Extracts the type variables from a signature * * @param signature * the signature to extract the type variables from * @param variables * the set of variables to fill in */ private static void extractTypeVariables(final String signature, final Set<String> variables) { Assert.isNotNull(signature); Assert.isNotNull(variables); final String[] arguments= Signature.getTypeArguments(signature); if (arguments.length == 0) { variables.add(Signature.toString(signature)); } else { for (int index= 0; index < arguments.length; index++) variables.add(Signature.toString(arguments[index])); } } /** * Returns the type variables referenced in the signature of the specified member. * * @param declaring * The declaring type of the specified member * @param member * the member to get its type variables. Can be a type, field or a method. * @return a possibly empty array of type variable candidates * @throws JavaModelException * if the signature of the specified member could not be resolved */ private static String[] getReferencedVariables(final IType declaring, final IMember member) throws JavaModelException { Assert.isNotNull(declaring); Assert.isNotNull(member); final String[] variables= parametersToVariables(declaring.getTypeParameters()); String[] result= new String[0]; if (member instanceof IField) { final String signature= ((IField) member).getTypeSignature(); final String[] signatures= getVariableSignatures(signature); if (signatures.length == 0) { final String variable= Signature.toString(signature); for (int index= 0; index < variables.length; index++) { if (variable.equals(variables[index])) { result= new String[] { variable}; break; } } } else { result= new String[signatures.length]; for (int index= 0; index < result.length; index++) result[index]= Signature.toString(signatures[index]); } } else if (member instanceof IMethod) { final IMethod method= (IMethod) member; final HashSet<String> set= new HashSet<String>(); final String[] types= method.getParameterTypes(); for (int index= 0; index < types.length; index++) extractTypeVariables(types[index], set); extractTypeVariables(method.getReturnType(), set); final String[] arguments= parametersToVariables(((IMethod) member).getTypeParameters()); for (int index= 0; index < arguments.length; index++) set.add(arguments[index]); result= new String[set.size()]; set.toArray(result); } else if (member instanceof IType) result= parametersToVariables(((IType) member).getTypeParameters()); else { JavaPlugin.logErrorMessage("Unexpected sub-type of IMember: " + member.getClass().getName()); //$NON-NLS-1$ Assert.isTrue(false); } final List<String> list= new ArrayList<String>(variables.length); String variable= null; for (int index= 0; index < variables.length; index++) { variable= variables[index]; for (int offset= 0; offset < result.length; offset++) if (variable.equals(result[offset])) list.add(result[offset]); } result= new String[list.size()]; list.toArray(result); return result; } /** * Returns all type variable names of the indicated member not mapped by the specified type variable mapping. * * @param mapping * the type variable mapping. The entries of this mapping must be simple type variable names. * @param declaring * the declaring type of the indicated member * @param member * the member to determine its unmapped type variable names * @return a possibly empty array of unmapped type variable names * @throws JavaModelException * if the type parameters of the member could not be determined */ public static String[] getUnmappedVariables(final TypeVariableMaplet[] mapping, final IType declaring, final IMember member) throws JavaModelException { Assert.isNotNull(mapping); Assert.isNotNull(declaring); Assert.isNotNull(member); List<String> list= null; final String[] types= getReferencedVariables(declaring, member); if (mapping.length == 0) { list= new ArrayList<String>(types.length); list.addAll(Arrays.asList(types)); } else { final Set<String> mapped= new HashSet<String>(types.length); String type= null; for (int index= 0; index < types.length; index++) { for (int offset= 0; offset < mapping.length; offset++) { type= types[index]; if (mapping[offset].getSourceName().equals(type)) mapped.add(type); } } list= new ArrayList<String>(types.length - mapped.size()); for (int index= 0; index < types.length; index++) { type= types[index]; if (!mapped.contains(type)) list.add(type); } } final String[] result= new String[list.size()]; list.toArray(result); return result; } /** * Returns the type variable signatures of the specified parameterized type signature, or an empty array if none. * * @param signature * the signature to get its type variable signatures from. The signature must be a parameterized type signature. * @return a possibly empty array of type variable signatures * @see Signature#getTypeArguments(String) */ private static String[] getVariableSignatures(final String signature) { Assert.isNotNull(signature); String[] result= null; try { result= Signature.getTypeArguments(signature); } catch (IllegalArgumentException exception) { result= new String[0]; } return result; } /** * Returns the reversed type variable mapping of the specified mapping. * * @param mapping * the mapping to inverse * @return the possibly empty inverse mapping */ public static TypeVariableMaplet[] inverseMapping(final TypeVariableMaplet[] mapping) { Assert.isNotNull(mapping); final TypeVariableMaplet[] result= new TypeVariableMaplet[mapping.length]; TypeVariableMaplet maplet= null; for (int index= 0; index < mapping.length; index++) { maplet= mapping[index]; result[index]= new TypeVariableMaplet(maplet.getTargetName(), maplet.getTargetIndex(), maplet.getSourceName(), maplet.getSourceIndex()); } return result; } /** * Creates a type variable mapping from a domain to a range. * * @param domain * the domain of the mapping * @param range * the range of the mapping * @param indexes * <code>true</code> if the indexes should be compared, <code>false</code> if the names should be compared * @return a possibly empty type variable mapping */ private static TypeVariableMaplet[] parametersToSignatures(final ITypeParameter[] domain, final String[] range, final boolean indexes) { Assert.isNotNull(domain); Assert.isNotNull(range); final Set<TypeVariableMaplet> set= new HashSet<TypeVariableMaplet>(); ITypeParameter source= null; String target= null; String element= null; String signature= null; for (int index= 0; index < domain.length; index++) { source= domain[index]; for (int offset= 0; offset < range.length; offset++) { target= range[offset]; element= source.getElementName(); signature= Signature.toString(target); if (indexes) { if (offset == index) set.add(new TypeVariableMaplet(element, index, signature, offset)); } else { if (element.equals(signature)) set.add(new TypeVariableMaplet(element, index, signature, offset)); } } } final TypeVariableMaplet[] result= new TypeVariableMaplet[set.size()]; set.toArray(result); return result; } /** * Converts the specified type parameters to type variable names. * * @param parameters * the type parameters to convert. * @return the converted type variable names * @see ITypeParameter#getElementName() */ private static String[] parametersToVariables(final ITypeParameter[] parameters) { Assert.isNotNull(parameters); String[] result= new String[parameters.length]; for (int index= 0; index < parameters.length; index++) result[index]= parameters[index].getElementName(); return result; } /** * Creates a type variable mapping from a domain to a range. * * @param domain * the domain of the mapping * @param range * the range of the mapping * @return a possibly empty type variable mapping */ private static TypeVariableMaplet[] signaturesToParameters(final String[] domain, final ITypeParameter[] range) { Assert.isNotNull(domain); Assert.isNotNull(range); Assert.isTrue(domain.length == 0 || domain.length == range.length); final List<TypeVariableMaplet> list= new ArrayList<TypeVariableMaplet>(); String source= null; String target= null; for (int index= 0; index < domain.length; index++) { source= Signature.toString(domain[index]); target= range[index].getElementName(); list.add(new TypeVariableMaplet(source, index, target, index)); } final TypeVariableMaplet[] result= new TypeVariableMaplet[list.size()]; list.toArray(result); return result; } /** * Returns a type variable mapping from a subclass to a superclass. * * @param type * the type representing the subclass class * @return a type variable mapping. The mapping entries consist of simple type variable names. * @throws JavaModelException * if the signature of one of the types involved could not be retrieved */ public static TypeVariableMaplet[] subTypeToInheritedType(final IType type) throws JavaModelException { Assert.isNotNull(type); final ITypeParameter[] domain= type.getTypeParameters(); if (domain.length > 0) { final String signature= type.getSuperclassTypeSignature(); if (signature != null) { final String[] range= getVariableSignatures(signature); if (range.length > 0) return parametersToSignatures(domain, range, false); } } return new TypeVariableMaplet[0]; } /** * Returns a type variable mapping from a subclass to a superclass. * * @param subtype * the type representing the subclass * @param supertype * the type representing the superclass * @return a type variable mapping. The mapping entries consist of simple type variable names. * @throws JavaModelException * if the signature of one of the types involved could not be retrieved */ public static TypeVariableMaplet[] subTypeToSuperType(final IType subtype, final IType supertype) throws JavaModelException { Assert.isNotNull(subtype); Assert.isNotNull(supertype); final TypeVariableMaplet[] mapping= subTypeToInheritedType(subtype); if (mapping.length > 0) { final ITypeParameter[] range= supertype.getTypeParameters(); if (range.length > 0) { final String signature= subtype.getSuperclassTypeSignature(); if (signature != null) { final String[] domain= getVariableSignatures(signature); if (domain.length > 0) return composeMappings(mapping, signaturesToParameters(domain, range)); } } } return mapping; } /** * Returns a type variable mapping from a superclass to a subclass. * * @param supertype * the type representing the superclass * @param subtype * the type representing the subclass * @return a type variable mapping. The mapping entries consist of simple type variable names. * @throws JavaModelException * if the signature of one of the types involved could not be retrieved */ public static TypeVariableMaplet[] superTypeToInheritedType(final IType supertype, final IType subtype) throws JavaModelException { Assert.isNotNull(subtype); Assert.isNotNull(supertype); final ITypeParameter[] domain= supertype.getTypeParameters(); if (domain.length > 0) { final String signature= subtype.getSuperclassTypeSignature(); if (signature != null) { final String[] range= getVariableSignatures(signature); if (range.length > 0) return parametersToSignatures(domain, range, true); } } return new TypeVariableMaplet[0]; } /** * Returns a type variable mapping from a superclass to a subclass. * * @param supertype * the type representing the superclass * @param subtype * the type representing the subclass * @return a type variable mapping. The mapping entries consist of simple type variable names. * @throws JavaModelException * if the signature of one of the types involved could not be retrieved */ public static TypeVariableMaplet[] superTypeToSubType(final IType supertype, final IType subtype) throws JavaModelException { Assert.isNotNull(supertype); Assert.isNotNull(subtype); return inverseMapping(subTypeToSuperType(subtype, supertype)); } private TypeVariableUtil() { // Not to be instantiated } }